import { Device } from '@app/db/entities/Device';
import { PrivateDevice } from '@app/db/entities/PrivateDevice';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DataSource, FindOptionsWhere, In, LessThan, Repository } from 'typeorm';
import { DeviceService } from '../device/device.service';
import { ConfigureService } from '@app/configure';
import { CacheService } from '@app/cache';
import { PrivateChargeRecord } from '@app/db/entities/PrivateChargeRecord';
import { CommonService } from '../common/common.service';
import { PrivateChargePowerData, PrivateChargePowerDataDocument } from '@app/mongodb/schemas/privateChargePowerData';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { UserService } from '../user/user.service';
import { UtilsService } from '@app/utils';

@Injectable()
export class PrivateDeviceService {
    constructor(
        @InjectRepository(PrivateDevice) protected repository:Repository<PrivateDevice>,
        @InjectRepository(PrivateChargeRecord) protected privateChargeRecord:Repository<PrivateChargeRecord>,
        @InjectModel(PrivateChargePowerData.name) public readonly privateChargePowerData: Model<PrivateChargePowerDataDocument>,
        private readonly device:DeviceService,
        private readonly config:ConfigureService,
        private readonly cache:CacheService,
        private readonly common:CommonService,
        protected dataSource: DataSource,
        private readonly user:UserService,
        private readonly utils:UtilsService
    ){}


    async chargePage(query:Pagination, deviceId:number) {
        let where:FindOptionsWhere<PrivateChargeRecord> = { deviceId }
        const lastId = query.lastKey || 0
        const pageSize = query.pageSize || 20

        if(lastId > 0) {
            where.id = LessThan(lastId)
        }

        let rows:PrivateChargeRecord[] = await this.privateChargeRecord.find({
            where,
            skip : 0, 
            take : pageSize, 
            order : {id : 'DESC'}
        })

        return rows
    }

    async charging(deviceId:number, init:number) {
        const key = this.config.get('PdeviceChargingOrderKey') + deviceId
        const recordIds = await this.cache.client.smembers(key)
        if(!recordIds || recordIds.length == 0) return []
        const records = await this.privateChargeRecord.find({
            where : {id : recordIds.length == 1 ? Number(recordIds[0]) : In(recordIds)},
            select : ['id', 'status', 'deviceId', 'port', 'feeType', 'precharge']
        })
        
        //要删除的id
        const outRecordIds:number[] = []
        const deviceIds:number[] = []
        records.forEach(record => {
            if(record.status == 1) {
                deviceIds.push(record.deviceId)
            }else {
                outRecordIds.push(record.id)
            }
        })

        if(outRecordIds.length > 0) {
            this.cache.client.srem(key, ...outRecordIds)
        }

        if(deviceIds.length == 0) return []

        let devices = await this.device.find({id : deviceIds.length == 1 ? deviceIds[0] : In(deviceIds)}, ['id', 'chargeType', 'portData', 'aliProduct'])
        const deviceMap:{[key:string] : Device} = {}
        devices.forEach(device => {
            if(init == 1) {
                this.device.queryDeviceChargingStatus(device.id, device.aliProduct)
            }
            
            deviceMap[device.id] = device
        })
        devices = null
        
        const rows:any = []
        
        records.forEach(order => {
            if(order.status == 1) {
                let row:any = {...order}
                const device = deviceMap[order.deviceId]
                if(device) {
                    row.device = {
                        chargeType : device.chargeType
                    }
                    if(device.portData) {
                        row.device.port = device.portData[order.port - 1]
                    }
                }
                rows.push(row)
            }
        })

        return rows
    }

    async detail(id:number) {
        const order = await this.privateChargeRecord.findOne({
            where : {id}
        })
        //const device = await this.device.findOne({id : order.deviceId}, ['chargeType'])
        const stopReason = await this.common.queryChargeStopReason(order.stopReason)
        return {
            ...order,
            stopReason,
        }
    }
    
    async list(userId:number) {
        const query = this.repository.createQueryBuilder()
        query.select(['PrivateDevice.owner', 'PrivateDevice.id'])
        query.andWhere(`JSON_CONTAINS(PrivateDevice.owners, '${userId}')`)
        const rows = await query.getMany()
        if(rows.length > 0) {
            const ids = rows.map(row => row.id)
            const devices:(Device & {own? : boolean})[] = await this.device.find({id : ids.length == 1 ? ids[0] : In(ids)}, ['id', 'connectStatus'])
            devices.forEach(device => {
                const i = rows.findIndex(row => row.id == device.id)
                device.own = rows[i].owner == userId
                rows.splice(i, 1)
            })

            return devices
        }


        return []

    }

    async bind(userId:number, deviceId:number) {
        const queryRunner = await this.dataSource.createQueryRunner()
        await queryRunner.connect();
        await queryRunner.startTransaction();

        try {
            const device = await queryRunner.manager.findOne<Device>(Device, {
                where : {id : deviceId},
                select : ['private', 'id', 'owners'],
                lock : {
                    mode : 'pessimistic_read'
                }
            })

            if(!device) {
                await queryRunner.rollbackTransaction();
                return null
            }

            if(device.owners != null && device.owners.length > 0) {
                await queryRunner.rollbackTransaction();
                return null
            }
            
            if(device.private == 1) {
                await queryRunner.rollbackTransaction();
                return -1
            }

            //把设备改成私人设备
            const r1 = await queryRunner.manager.update<Device>(Device, { id : deviceId }, {private : 1})
            if(r1.affected < 1) {
                await queryRunner.rollbackTransaction();
                return -2
            }


            const { raw } = await queryRunner.manager.insert<PrivateDevice>(PrivateDevice, {
                id : deviceId,
                owner : userId,
                owners : [userId]
            })

            if(!raw || raw.affectedRows < 1) {
                await queryRunner.rollbackTransaction();
                return -3
            }

            await queryRunner.commitTransaction()

            return true

        } catch (error) {
            console.log(error)
            await queryRunner.rollbackTransaction();
            return null
        } finally {
            await queryRunner.release();
        }
    }

    async unbind(userId:number, deviceId:number) {
        const device = await this.device.queryDeviceById(deviceId, ['id', 'private'])
        if(!device || device.private == 0) return null

        const privatDevice = await this.repository.findOne({
            where : {id : deviceId},
            select : ['owner']
        })

        if(!privatDevice || privatDevice.owner != userId) return null

        const queryRunner = await this.dataSource.createQueryRunner()
        await queryRunner.connect();
        await queryRunner.startTransaction();


        try {
            
            //把设备改成非私人设备
            const r1 = await queryRunner.manager.update<Device>(Device, { id : deviceId }, {private : 0})
            if(r1.affected < 1) {
                await queryRunner.rollbackTransaction();
                return -1
            }

            const r2 = await queryRunner.manager.delete<PrivateDevice>(PrivateDevice, { id : deviceId })

            if(r2.affected < 1) {
                await queryRunner.rollbackTransaction();
                return -2
            }

            await queryRunner.commitTransaction()

            return true

        } catch (error) {
            console.log(error)
            await queryRunner.rollbackTransaction();
            return null
        } finally {
            await queryRunner.release();
        }
    }

    async members(deviceId:number) {
        const device = await this.repository.findOne({
            where : {id : deviceId},
            select : ['owners']
        })
        if(!device) return []
        device.owners.splice(0, 1)
        if(device.owners.length == 0) return []
        const users = await this.user.find({id : device.owners.length == 1 ? device.owners[0] : In(device.owners)}, ['id', 'nickname', 'avatar', 'phone'])
        users.forEach(user => {
            if(user.phone != null) {
                user.phone = this.utils.maskPhoneNumber(user.phone)
            }
            
        })

        return users
    }

    async bindMember(userId:number, phone:string, deviceId:number) {
        const user = await this.user.findOne({phone : phone}, ['id'])
        if(!user) return null
        const device = await this.repository.findOne({
            where : {id : deviceId},
            select : ['owner', 'owners']
        })

        if(!device || device.owner != userId || device.owners.includes(user.id)) return false

        device.owners.push(user.id)

        const r = await this.repository.update({id : deviceId}, {
            owners : device.owners
        })

        return r.affected > 0

    }

    async unbindMember(userId:number, toUserId:number, deviceId:number) {
        const device = await this.repository.findOne({
            where : {id : deviceId},
            select : ['owner', 'owners']
        })

        if(!device || device.owner != userId) return null

        const index = device.owners.indexOf(toUserId)
        if(index < 0) return null

        device.owners.splice(index, 1)

        const r = await this.repository.update({id : deviceId}, {
            owners : device.owners
        })

        return r.affected > 0
    }

    powerData(id:number, pageNo:number, pageSize:number) {
        return this.privateChargePowerData.findOne({id : id}, {_id : 0, data : {$slice : ['$data', (pageNo - 1) * pageSize, pageSize]}})
    }
}
